package ar.edu.itba.it.cg.grupo01.impl;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import ar.edu.itba.it.cg.grupo01.api.Intersection;
import ar.edu.itba.it.cg.grupo01.api.Ray;
import ar.edu.itba.it.cg.grupo01.api.shape.Shape;
import ar.edu.itba.it.cg.grupo01.impl.shape.Box;
import ar.edu.itba.it.cg.grupo01.impl.shape.Triangle;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.Shader;

public class IntersectionImpl implements Intersection<Shape> {

	private Set<Point3f> points;
	private Shape object;
	private Intersection.Type type;

	public IntersectionImpl(Collection<Point3f> points, Shape shape) {
		this(points,shape,Intersection.Type.OUT);
	}

	public IntersectionImpl(Collection<Point3f> points, Shape shape, Intersection.Type type) {
		this.points = new HashSet<Point3f>(points);
		this.object = shape;
		this.type = type;
	}

	@Override
	public Set<Point3f> getIntersections() {
		return points;
	}

	@Override
	public boolean intersects() {
		return !points.isEmpty();
	}

	@Override
	public Shape getObject() {
		return object;
	}

	@Override
	public float distance(Point3f point) {

		float distance = Float.MAX_VALUE;
		for (Point3f intersections : points) {
			float currentDistance = intersections.distance(point);

			if (currentDistance < distance) {
				distance = currentDistance;
			}
		}

		return distance;
	}

	@Override
	public Point3f closestTo(Point3f origin) {
		
		Point3f answer = null;
		float currentDistance = Float.MAX_VALUE;
		
		for (Point3f point : points) {
			float distance = point.distance(origin);
			
			if( distance < currentDistance ) {
				currentDistance = distance;
				answer = point;
			}
		}
		
		
		return answer == null ? null : (Point3f) answer.clone();
	}

	@Override
	public Intersection<? extends Shape> getClosestIntersectionTo(Point3f point) {
		return this;
	}

	@Override
	public Intersection.Type getType() {
		return type;
	}

	@Override
	public void setType(Intersection.Type type) {
		this.type = type;
	}

	@Override
	public void getDetails(Shape shape, RayTraceInformation info, float delta, Details details) {

		Ray ray = info.getRay();
		Intersection<? extends Shape> intersection = info.getIntersection();
		Vector3f shapeNormal = info.getNormal();
		Vector3f rayDirection = ray.getDirection();

		if (shape instanceof Triangle) {
			if (shapeNormal.dot(rayDirection) > 0.0) {
				intersection.setType(Intersection.Type.IN);
			}
		}

		Vector3f normal = new Vector3f(shapeNormal);
		if (intersection.getType().equals(Intersection.Type.IN)) {
			normal.negate();
		}

		
		Shader shader = shape.getShader();
		
		float n1, n2;
		float n = 1.0f;
		float eta = shader.getRefractionIndex();

		if (shape instanceof Box) {
			if (type.equals(Intersection.Type.IN)) {
				n = 1.0f / eta;
				n1 = 1.0f;
				n2 = eta;
			} else {
				n = eta;
				n1 = eta;
				n2 = 1.0f;
			}
		} else {
			if (type.equals(Intersection.Type.OUT)) {
				n = 1.0f / eta;
				n1 = 1.0f;
				n2 = eta;
			} else {
				n = eta;
				n1 = eta;
				n2 = 1.0f;
			}
		}

		float cosI = -normal.dot(rayDirection);
		float cosT2 = 1.0f - n * n * (1.0f - cosI * cosI);
		float cosT = 0.0f;

		Vector3f refractedRayDir = new Vector3f();

		boolean skipRefract = cosT2 < 0.0;

		if (!skipRefract) {
			cosT = (float) Math.sqrt(cosT2);
			float refractedRayK = (n * cosI) - cosT;
			refractedRayDir.x = n * rayDirection.x + refractedRayK
					* normal.x;
			refractedRayDir.y = n * rayDirection.y + refractedRayK
					* normal.y;
			refractedRayDir.z = n * rayDirection.z + refractedRayK
					* normal.z;
			refractedRayDir.normalize();
		}

		float reflectionK = details.reflectionK;
		float refractionK = details.refractionK;
		
		float rParallelPart = (n1 * cosI - n2 * cosT)
				/ (n1 * cosI + n2 * cosT);
		float rNormalPart = (n2 * cosI - n1 * cosT)
				/ (n2 * cosI + n1 * cosT);
		reflectionK = 0.5f * (rParallelPart * rParallelPart + rNormalPart
				* rNormalPart);
		refractionK = 1 - reflectionK;

		Point3f origin = new Point3f();
		normal.scale(-delta);
		origin.add(info.getIntersectionPoint(), normal);

		Ray refractedRay = new RayImpl(origin, refractedRayDir);
		
		details.skipRefract = skipRefract;
		details.refractionK = refractionK;
		details.reflectionK = reflectionK;
		details.refractedRay = refractedRay;
		
	}

}
